
#include "gh.h"
#include "GL/wglew.h"

static HWND nHwnd = NULL;
static HDC nHdc = NULL;
static HGLRC nHrc = NULL;

static int nFrameCount = 0;
static double nLastTime, nLastSecond;

static LRESULT CALLBACK MsgProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
    switch (msg) {
    case WM_LBUTTONUP:
        ghAppMouseCallback(LOWORD(lparam), HIWORD(lparam), GH_MOUSE_UP);
        return 0;

    case WM_LBUTTONDOWN:
        ghAppMouseCallback(LOWORD(lparam), HIWORD(lparam), GH_MOUSE_DOWN);
        return 0;

    case WM_MOUSEMOVE:
        ghAppMouseCallback(LOWORD(lparam), HIWORD(lparam), GH_MOUSE_MOVE);
        return 0;

    case WM_KEYDOWN:
        if (wparam == VK_ESCAPE) {
            PostQuitMessage(0);
            return 0;
        }
        break;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hwnd, msg, wparam, lparam);
}

static int Run(void) {
    WNDCLASS wc;
    RECT rect;
    PIXELFORMATDESCRIPTOR pfd;
    MSG msg;
    int r;
#define WINDOW_STYLE (WS_OVERLAPPED | WS_SYSMENU | WS_CAPTION | WS_MINIMIZEBOX | WS_VISIBLE)

    /* window class */
    memset(&wc, 0, sizeof(wc));
    wc.lpfnWndProc = MsgProc;
    wc.hInstance = GetModuleHandle(NULL);
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
    wc.lpszClassName = GH_APP_NAME;

    if (!RegisterClass(&wc)) {
        GH_ERROR_SET("RegisterClass failed.");
        return -1;
    }

    /* window */
    SetRect(&rect, 0, 0, GH_APP_VIEWPORT_WIDTH, GH_APP_VIEWPORT_HEIGHT);
    AdjustWindowRect(&rect, WINDOW_STYLE, FALSE);

    if (!(nHwnd = CreateWindow(GH_APP_NAME, GH_APP_NAME, WINDOW_STYLE,
        GetSystemMetrics(SM_CXSCREEN) / 2 - (rect.right - rect.left) / 2,
        GetSystemMetrics(SM_CYSCREEN) / 2 - (rect.bottom - rect.top) / 2,
        rect.right - rect.left, rect.bottom - rect.top,
        NULL, NULL, NULL, 0))) {
        GH_ERROR_SET("CreateWindow failed.");
        return -1;
    }

    /* OpenGL context */
    memset(&pfd, 0, sizeof(pfd));
    pfd.nSize = sizeof(pfd);
    pfd.nVersion = 1;
    pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
    pfd.iPixelType = PFD_TYPE_RGBA;
    pfd.cColorBits = 24;
    pfd.cDepthBits = 24;
    pfd.cStencilBits = 8;

    if (!(nHdc = GetDC(nHwnd))) {
        GH_ERROR_SET("GetDC failed.");
        return -1;
    }

    if (!SetPixelFormat(nHdc, ChoosePixelFormat(nHdc, &pfd), &pfd)) {
        GH_ERROR_SET("SetPixelFormat failed.");
        return -1;
    }

    if (!(nHrc = wglCreateContext(nHdc))) {
        GH_ERROR_SET("wglCreateContext failed.");
        return -1;
    }

    if (!wglMakeCurrent(nHdc, nHrc)) {
        GH_ERROR_SET("wglMakeCurrent failed.");
        return -1;
    }

    /* GLEW */
    if (glewInit() != GLEW_OK) {
        GH_ERROR_SET("glewInit failed.");
        return -1;
    }

    if (wglSwapIntervalEXT) {
        wglSwapIntervalEXT(GH_APP_VSYNC);
    }

    if (ghAppBegin() < 0) {
        return -1;
    }

    /* timer */
    nLastTime = nLastSecond = ghTimeGetSec();

    memset(&msg, 0, sizeof(msg));
    while (msg.message != WM_QUIT) {
        if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        } else {
            double now, dt;
            now = ghTimeGetSec();
            dt = now - nLastTime;
            nLastTime = now;

            if (now - nLastSecond > 1.0) {
                char title[128];
                double fps = nFrameCount / (now - nLastSecond);

                sprintf(title, "Low-Level GPU Programming :: %s [fps: %f, ms: %f]",
                    GH_APP_NAME, fps, (now - nLastSecond) / nFrameCount * 1000);
                SetWindowText(nHwnd, title);

                nLastSecond = now;
                nFrameCount = 0;
            }

            r = ghAppFrame(dt, now);
            SwapBuffers(nHdc);
            nFrameCount++;

            if (glGetError() != GL_NO_ERROR) {
                GH_ERROR_SET("OpenGL error detected.");
                return -1;
            }
            if (r < 0) {
                GH_ERROR_SET("ghAppFrame failed.");
                return -1;
            }
            if (r > 0) {
                GH_TRACE("ghAppFrame ended.");
                return 0;
            }
        }
    }

    GH_TRACE("Main loop ended.");
    return 0;
}

double ghTimeGetSec(void) {
    LARGE_INTEGER ticksPerSec = { 0 };
    LARGE_INTEGER counter = { 0 };
    QueryPerformanceFrequency(&ticksPerSec);
    QueryPerformanceCounter(&counter);
    return counter.QuadPart / (double) ticksPerSec.QuadPart;
}

double ghTimeGetMicroSec(void) {
    return ghTimeGetSec() * 1000000.0;
}

int CALLBACK WinMain(HINSTANCE ignore0, HINSTANCE ignore1, LPSTR ignore2, int ignore3) {
    int result = 0;

    if (Run() < 0) {
        result = -1;
        MessageBox(NULL, ghErrorGetInfo(), "Runtime error", MB_OK | MB_ICONERROR);
    }

    if (ghAppEnd() < 0) {
        result = -1;
        MessageBox(NULL, ghErrorGetInfo(), "Runtime error", MB_OK | MB_ICONERROR);
    }

    if (nHrc) {
        wglMakeCurrent(NULL, NULL);
        wglDeleteContext(nHrc);
        nHrc = NULL;
    }
    if (nHdc) {
        ReleaseDC(nHwnd, nHdc);
        nHdc = NULL;
    }
    nHwnd = NULL;

    return result;
}
